/*
	Class: ContainerVideo
	
		Frontal Video Container
*/	
package com.frontalcode 
{
	import fl.video.*;
	import flash.events.*;

	/**                  
     * The ContainerVideo class handles the <video /> tag.
     */
    public class ContainerVideo extends Container
    {
		internal var video : FrontalFLVPlayback;
		internal var playStateAtDisable : Boolean;
		internal var videoReady : Boolean;
		internal var videoFailed : Boolean;
		internal var cuePoints : Array = [ ];
		
		/**
		 * Creates a new ContainerVideo instance.
		 * 
		 * @param document	The document this element belongs to.
		 * @param parent	The element's parent in the document tree.
		 * @param node		The XML node this element is associated with.
		 * @param options	Options affecting the behavior of this instance.
		 * 					(This object does not have built-in support for any
		 * 					options.)
		 */
		public function ContainerVideo ( document : Document, parent : DocumentElement, node : XML, options : Object = null )
		{
			super ( document, parent, node, options );
			
			videoReady = false;
		}
	
		/**
		 * @private
		 */
		override public function onFirstDocumentPreRender ( ) : void
		{
			super.onFirstDocumentPreRender ( );
			if ( video == null ) 
			{
				video = new FrontalFLVPlayback ( this );

				addListener ( video, Event.ADDED_TO_STAGE, videoStageChangeHandler );
				addListener ( video, Event.REMOVED_FROM_STAGE, videoStageChangeHandler );
				
				if ( getStyle ( "video-enable-with-show" ) ) enabled = false;
				
				movie.addChildAt ( video, 0 );
			}
		}

		/**
		 * @private
		 */
		public function videoStageChangeHandler ( event : Event ) : void
		{
			if ( getStyle ( "video-enable-with-show" ) ) enabled = event.type == Event.ADDED_TO_STAGE;
		}
		
		/**
		 * @private
		 */
		override public function applyStyles ( ) : void
		{
			if ( getStyle ( "wants-reset" ) !== false ) setStyle ( "wants-reset", true );

			// Apply video options first so that our content dimensions are 
			// available for the background, mask, etc.
			//
			if ( video != null ) setVideoOptions ( );
			
			super.applyStyles ( );
			
			if ( video != null )
			{
				var source : String = Util.isEmpty.test ( getAttribute ( "src" ) ) ? null : getAttribute ( "src" );
				if ( source != null ) source = AssetManager.getInstance ( ).getFullURL ( getAttribute ( "src" ), getStyle ( "base-url" ) );
				
				/* 
					If our src has changed then we need to change the source for our
					video. We do not need to remove our cue points first because as
					the documentation says:
					
					Cue point information is wiped out when the source property is
					set, so to set cue point information for the next FLV file to be
					loaded, set the source property first.
				*/
				
				if ( video.source != source )
				{
					videoFailed = false;
					video.source = source;
					cuePoints = [ ];
				}
				
				if ( video.source != null )
				{
					// We set our cue points every time since they are defined by
					// our attributes which may change.
					//
					
					removeListener ( video, "cuePoint", onVideoCuePoint );
					
					// We will add any new cue points and remove any old ones.
					//
					for ( var i : int = 0; i < cuePoints.length; i++ ) cuePoints [ i ].remove = true;
					
					for each ( var name : String in getAttributes ( ) ) 
					{
						if ( name.toLowerCase ( ).indexOf ( "cuepoint" ) == 0 )
						{
							var defn : RegExp = /^\s*(\d+(\.\d+)?|\.\d+),?(.*)$/;
							var result : Object = defn.exec ( getAttribute ( name ) );
							if ( result != null )
							{
								var time : Number = Number ( result [ 1 ] );
								var title : String = result [ 3 ];
								
								if ( ! cuePoints.some ( 
														function ( elem : *, index : int, arr : Array ) : Boolean 
														{ 
															if ( elem.name == name && elem.time == time )
															{
																elem.remove = false;
																return true;
															}
															return false;
														} ) 
													)
								{
									cuePoints.push ( { remove: false, name: name, time: time, title: title } );
									video.addASCuePoint ( cuePoints [ cuePoints.length - 1 ] );
								}
							}
						}
					}
					
					for ( i = 0; i < cuePoints.length; i++ )
					{
						if ( cuePoints [ i ].remove )
						{
							video.removeASCuePoint ( cuePoints [ i ] );
							cuePoints.splice ( i, 1 );
							i--;
						}
					}
					
					if ( cuePoints.length > 0 ) addListener ( video, "cuePoint", onVideoCuePoint );
					
					if ( videoReady && ! deepLinkInitialized ) processDeepLink ( checkForDeepLink ( ) );
				}
			}
		}
		
		/**
		 * @private
		 */
		override public function applyMovieStyles ( ) : void
		{
			super.applyMovieStyles ( );
			if ( video != null ) video.mouseEnabled = getStyle ( "mouse-enabled" ) !== undefined ? getStyle ( "mouse-enabled" ) : true;
		}
		
		/**
		 * @private
		 */
		override public function get intrinsicWidth ( ) : Number 
		{
			var value : Number;
			if ( video != null ) value = video.width;
			return value;
		}
		
		/**
		 * @private
		 */
		override public function get intrinsicHeight ( ) : Number 
		{ 
			var value : Number;
			if ( video != null ) value = video.height;
			return value;
		}
		
		/*
			Function: setVideoOptions
			
				Sets the properties for the video player by parsing local and 
				cascading styles. 
		*/	
		/**
		 * @private
		 */
		internal function setVideoOptions ( ) : void
		{
			for each ( var style : String in StylesManager.videoPropertyStyles )
			{
				var value : * = getStyle ( style );
				
				if ( value !== undefined )
				{
					var styleDefn : Object = document.stylesManager.getStyleDefn ( style );
					if ( style == "video-skin" ) value = AssetManager.getInstance ( ).getFullURL ( value, getStyle ( "base-url" ) );
					video [ styleDefn.options.videoProperty ] = value;
				}
			}
			
			video.align = VideoAlign.TOP_LEFT;
			video.registrationX = 0;
			video.registrationY = 0;
			
			var widthSpecified  : Boolean = ! isNaN ( width  ) && nonAutoWidthSpecified;
			var heightSpecified : Boolean = ! isNaN ( height ) && nonAutoHeightSpecified;
			
			if ( widthSpecified && ! heightSpecified )
			{
				// Handle the situation where we have a width but no height.
				//
				if ( videoReady )
				{
					video.scaleMode = VideoScaleMode.MAINTAIN_ASPECT_RATIO;
					video.registrationWidth  = width;
					video.registrationHeight = ( width / video.preferredWidth ) * video.preferredHeight;
				}
			}
			else if ( heightSpecified && ! widthSpecified )
			{
				// Handle the situation where we have a height but no width.
				//
				if ( videoReady )
				{
					video.scaleMode = VideoScaleMode.MAINTAIN_ASPECT_RATIO;
					video.registrationWidth  = ( height / video.preferredHeight ) * video.preferredWidth;
					video.registrationHeight = height;
				}
			}
			else if ( widthSpecified && heightSpecified )
			{
				if ( getStyle ( "resize-scale" ) == "none" )
				{
					video.scaleMode = VideoScaleMode.NO_SCALE;
					if ( videoReady )
					{
						video.registrationWidth  = video.preferredWidth;
						video.registrationHeight = video.preferredHeight;
					}
				}
				else if ( getStyle ( "resize-scale" ) == "showmost" || getStyle ( "resize-scale" ) == "showall" )
				{
					video.scaleMode = VideoScaleMode.MAINTAIN_ASPECT_RATIO;
					
					if ( videoReady )
					{
						// We need to wait for the ready event when we will have the 
						// video dimensions before we can properly set the 
						// registrationWidth and registrationHeight.
						//
						if ( getStyle ( "resize-scale" ) == "showmost" )
						{
							var maxScale : Number = Math.max ( width / video.preferredWidth, height / video.preferredHeight );
							video.registrationWidth  = maxScale * video.preferredWidth;
							video.registrationHeight = maxScale * video.preferredHeight;
						}
						else
						{
							var minScale : Number = Math.min ( width / video.preferredWidth, height / video.preferredHeight );
							video.registrationWidth  = minScale * video.preferredWidth;
							video.registrationHeight = minScale * video.preferredHeight;
						}
					}
				}
				else if ( getStyle ( "resize-scale" ) == "stretch" )
				{
					video.scaleMode = VideoScaleMode.EXACT_FIT;
					video.registrationWidth  = width;
					video.registrationHeight = height;
				}
			}
			else
			{
				video.scaleMode = VideoScaleMode.NO_SCALE;
				if ( videoReady )
				{
					video.registrationWidth  = video.preferredWidth;
					video.registrationHeight = video.preferredHeight;
				}
			}
	
			// resize-scale-origin is a combination of top, left, 
			// bottom, right, center (horizontal) and middle (vertical).
			//
			var origin : String = getStyle ( "resize-scale-origin" );
			
			if ( videoReady && origin != null )
			{
				origin = origin.toLowerCase ( );

				if ( origin.indexOf ( "center" ) >= 0 )
				{
					video.registrationX = ( width  - video.registrationWidth  ) / 2;
				}
				else if ( origin.indexOf ( "right" ) >= 0 )
				{
					video.registrationX = width - video.registrationWidth;
				}
	
				if ( origin.indexOf ( "middle" ) >= 0 )
				{
					video.registrationY = ( height - video.registrationHeight ) / 2;
				}
				else if ( origin.indexOf ( "bottom" ) >= 0 )
				{
					video.registrationY = height - video.registrationHeight;
				}
			}
				
			// Preserve our disabled state.
			//
			
			if ( ! enabled )
			{
				playStateAtDisable = getStyle ( "video-auto-play" );
			}
			
			addListener ( video, VideoEvent.COMPLETE, onVideoComplete );
			addListener ( video, VideoEvent.SKIN_LOADED, onVideoSkinLoaded );
			addListener ( video, VideoEvent.READY, onVideoReady );
			addListener ( video, VideoEvent.STATE_CHANGE, onVideoStateChange );
		}
		
		/*
			Function: onVideoComplete
				
				FLVPlayback has completed the video.
		*/	
		/**
		 * @private
		 */
		public function onVideoComplete ( event : VideoEvent ) : void
		{
			if ( getStyle ( "video-loop" ) )
			{
				if ( ! video.autoRewind )
				{
					video.seek ( 0 );
				}
				
				if ( enabled )
				{
					video.play ( );
				}
				else
				{
					// Preserve our disabled state.
					//
					
					playStateAtDisable = true;
				}
			}
					
			runInteractions ( 'onVideoComplete', { event: event } );
		}
		
		/*
			Function: onVideoReady
				
				FLVPlayback callback when the video is ready to play.
		*/	
		/**
		 * @private
		 */
		public function onVideoReady ( event : VideoEvent ) : void
		{
			videoReady = true;
			needsRender = true;

			if ( ! enabled ) video.pause ( );
			
			runInteractions ( 'onVideoReady', { event: event } );
		}
			
		/*
			Function: onVideoSkinLoaded
				
				FLVPlayback callback when the video skin is loaded.
		*/	
		/**
		 * @private
		 */
		public function onVideoSkinLoaded ( event : VideoEvent ) : void
		{
			// XXX If we are disabled then also disable this just loaded skin?
			//

			runInteractions ( 'onVideoSkinLoaded', { event: event } );
		}
		
		/*
			Function: stateChange
				
				FLVPlayback callback when the video's state has changed.
		*/	
		/**
		 * @private
		 */
		public function onVideoStateChange ( event : VideoEvent ) : void
		{
			if ( event.state == VideoState.CONNECTION_ERROR )
			{
				videoFailed = true;
			}
			
			runInteractions ( 'onVideoStateChange', { event: event } );
		}
			
		/*
			Function: onVideoCuePoint
				
				FLVPlayback callback when a cue point is reached.
		*/	
		/**
		 * @private
		 */
		public function onVideoCuePoint ( event : MetadataEvent ) : void
		{
			runCuePoint ( event.info );
		}
		
		public function runCuePoint ( cue : Object ) : void
		{
			cue.taskId = Scheduler.getInstance ( ).removeTask ( cue.taskId );
			
			runInteractions ( 'on' + cue.name, { cue: cue } );
			runInteractions ( 'onAnyCuePoint', { cue: cue } );
			
			if ( getStyle ( "deep-link" ) == "path"  ) DeepLinkManager.updateDeepLink ( this, getDeepLinkIndex ( cue ), ( node.@name != undefined ? node.@name : "" ) + cue.title );				
			if ( getStyle ( "deep-link" ) == "query" ) DeepLinkManager.updateDeepLink ( this, getDeepLinkIndex ( cue ), null );				
		}
			
		/**
		 * @private
		 */
		override public function reset ( ) : void
		{
			super.reset ( );

			if ( video.source != null )
			{			
				if ( video.playheadTime > 0 && getStyle ( "video-auto-rewind" ) ) video.seek ( 0 );
				
				if ( getStyle ( "video-auto-play" ) )
				{
					if ( enabled )
					{
						video.play ( );
					}
					else
					{
						// Preserve our disabled state.
						//
						playStateAtDisable = true;
					}
				}
			}
		}
		
		/*
			Function: getMappedStyleValue
			
				In cases where the style maps to a video property, then we can 
				get a current value for it.
				
			Parameters:
			
				style	- The style of interest.
				
			Returns:
			
				The mapped value of the style.
		*/
		/**
		 * @private
		 */
		override public function getMappedStyleValue ( style : String ) : *
		{
			if ( video != null )
			{
				var styleDefn : Object = document.stylesManager.getStyleDefn ( style );
				if ( styleDefn.options.videoProperty != null ) return video [ styleDefn.options.videoProperty ];
			}
			
			return super.getMappedStyleValue ( style );
		}
		
		/**
		 * @private
		 */
		override public function set enabled ( value : Boolean ) : void 
		{
			if ( value != super.enabled )
			{
				super.enabled = value;
				
				if ( video != null )
				{
					if ( ! value )
					{
						// We need to be careful because we might be disabled multiple
						// times but we want to preserve the playing state from the
						// first time. This just means preserving a true value.
						//
						
						if ( ! playStateAtDisable )
						{
							playStateAtDisable  = video.playing;
						}
						
						if ( video.playing )
						{
							video.pause ( );
						}
					}
					else if ( playStateAtDisable )
					{
						video.play ( );
						
						// Reset our state to false. It will be corrected next time we
						// disabled the container.
						//
						
						playStateAtDisable = false;
					}
				}
			}
		}
		
		/*
			Function: getDeepLinkValue
			
				Get the cue point associated with the deep link index.
			
			Parameters:
			
				index	- The deep link index.
				
			Returns:
			
				The indexed Container.
		*/
		/**
		 * @private
		 */
		override public function getDeepLinkValue ( index : String ) : *  
		{ 
			if ( video == null ) return null;
			
			// Strip the preceding slash.
			//
			if ( getStyle ( "deep-link" ) == "path" && index.indexOf ( "/" ) == 0 ) index = index.substr ( 1 );
			
			var cue : Object = video.findCuePoint ( index );
			if ( cue == null && ! isNaN ( Number ( index ) ) ) cue = video.findCuePoint ( Number ( index ) ); 
			return cue; 
		}
		
		/*
			Function: getDeepLinkIndex
			
				Get the index for a cue point.
			
			Parameters:
			
				index	- The cue point.
				
			Returns:
			
				The index of the cue point.
		*/
		/**
		 * @private
		 */
		override public function getDeepLinkIndex ( value : * ) : String  
		{ 
			return value.name != null && value.name.length > 0 ? value.name : value.time; 
		}
		
		/*
			Function: processDeepLink
			
				Jump to the managed Container indexed by the deep link.
			
			Parameters:
			
				index	- The deep link index.
		*/
		/**
		 * @private
		 */
		override public function processDeepLink ( value : * ) : void 
		{ 
			if ( value != null )
			{
				var cue : Object = value.array [ value.index ];
				if ( video.isFLVCuePointEnabled ( cue ) ) 
				{
					video.seek ( cue.time );
					
					// It seems the cue point is not triggered when we seek to
					// it. So we set a brief timer that will simulate it's being
					// triggered. If it is triggered "naturally" we remove this
					// timer.
					//
					cue.taskId = Scheduler.setTimeoutInFrames ( runCuePoint, 5, cue );
				}
			}
			super.processDeepLink ( value );
		}
	}
}
